package com.haiyou.data.common.persister.mysql.help;

import java.io.Serializable;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.Type;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;

import org.apache.commons.lang3.Validate;
import org.apache.commons.lang3.tuple.ImmutablePair;

import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import com.google.common.util.concurrent.AtomicDouble;
import com.haiyou.common.system.Properties;
import com.haiyou.common.system.SystemProperties;
import com.haiyou.common.util.collect.ArrayUtils;
import com.haiyou.common.util.collect.CollectionUtils;
import com.haiyou.common.util.collect.SortUtils;
import com.haiyou.common.util.hash.HashUtils;
import com.haiyou.common.util.object.ClassUtils;
import com.haiyou.common.util.reflect.AnnotationFinder;
import com.haiyou.common.util.string.StringUtils;
import com.haiyou.data.common.persister.mysql.annotation.Blob;
import com.haiyou.data.common.persister.mysql.annotation.Column;
import com.haiyou.data.common.persister.mysql.annotation.Comment;
import com.haiyou.data.common.persister.mysql.annotation.Customize;
import com.haiyou.data.common.persister.mysql.annotation.Id;
import com.haiyou.data.common.persister.mysql.annotation.Index;
import com.haiyou.data.common.persister.mysql.annotation.Length;
import com.haiyou.data.common.persister.mysql.annotation.Nullable;
import com.haiyou.data.common.persister.mysql.annotation.Scale;
import com.haiyou.data.common.persister.mysql.annotation.Table;
import com.haiyou.data.common.persister.mysql.annotation.Text;
import com.haiyou.data.common.persister.mysql.annotation.Transient;
import com.haiyou.data.common.persister.mysql.dao.BaseDaoImpl;
import com.haiyou.data.common.persister.mysql.meta.ColumnMeta;
import com.haiyou.data.common.persister.mysql.meta.DBColumnMeta;
import com.haiyou.data.common.persister.mysql.meta.DatabaseMeta;
import com.haiyou.data.common.persister.mysql.meta.IdColumnMeta;
import com.haiyou.data.common.persister.mysql.meta.IndexMeta;
import com.haiyou.data.common.persister.mysql.meta.TableMeta;
import com.haiyou.data.common.persister.mysql.persister.roll.RollEntity;
import com.haiyou.data.common.persister.mysql.annotation.Length.Len;
import com.haiyou.data.common.persister.mysql.annotation.Table.RollPolicy;
import com.haiyou.data.common.persister.mysql.annotation.Index.IndexPolicy;

import lombok.extern.slf4j.Slf4j;

/**
 * table options
 * 
 * @author Administrator
 *
 */
@Slf4j
public class TableHelper {

	public static final Map<Class<?>, TableMeta> tableMetaPool = Maps.newHashMap();

	public static TableMeta getTableMeta(Class<?> entityClass) {
		return tableMetaPool.get(entityClass);
	}

	private static TableMeta parseTableMeta(DatabaseMeta databaseMeta, Class<?> entityClass) {
		return tableMetaPool.computeIfAbsent(entityClass, k -> {
			Table table = AnnotationFinder.findAnnotation(entityClass, Table.class);

			if(table.name().equals("t_gift_code")) {
				System.err.print(false);
			}
			
			String tableSimpleName = StringUtils.isEmpty(table.name())
					? StringUtils.humpToUnderline(entityClass.getSimpleName())
					: table.name();

			for (int index = 0; index < table.name().length(); index++) {
				char c = table.name().charAt(index);
				Validate.isTrue((c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || (StringUtils.isSymbol(c)),
						"表名【%s】命名必须是小写或者小写拼接数字", table.name());
			}

			if (databaseMeta.getCluster() > 1) {// 分库
				if (table.rollPolicy() != RollPolicy.NONE)// 滚表
					throw new RuntimeException(
							"db.cluster（分库策略） 和 tab.rollPolicy（滚表策略） 不可以同时存在，entityClass=" + entityClass);
			}
			if (table.cluster() > 1) {// 分表
				if (table.rollPolicy() != RollPolicy.NONE)// 滚表
					throw new RuntimeException(
							"tab.cluster（分表策略） 和 tab.rollPolicy（滚表策略） 不可以同时存在，entityClass=" + entityClass);

				if (databaseMeta.getCluster() > 1) {// 分库
					int dbCluster = databaseMeta.getCluster();
					int tabCluster = table.cluster();
					int min = Math.min(dbCluster, tabCluster);
					int max = Math.max(dbCluster, tabCluster);
					// TODO
					// 因为库和表的确定是用主键的hash再对数量取模得出，如果分库数量和分表数量相等的话，那么一个库里只会有一个表被写入数据
					Validate.isTrue(max % min != 0, "【%s】分库数量【%s】和分表数量【%s】不可以是整除关系", entityClass, dbCluster,
							tabCluster);
				}
			}

			TableMeta tableMeta = new TableMeta();

			tableMeta.setEngine(databaseMeta.getEngine());
			tableMeta.setCharset(databaseMeta.getCharset());
			tableMeta.setCollate(databaseMeta.getCollate());

			tableMeta.setColumnDropable(table.columnDropable());
			tableMeta.setComment(table.comment().value());
			tableMeta.setView(table.view());
			tableMeta.setEntityClassName(entityClass.getName());
			tableMeta.setSimpleName(tableSimpleName);
			tableMeta.setMappedSuperclass(table.mappedSuperclass());
			tableMeta.setCluster(table.cluster());
			tableMeta.setRollPolicy(table.rollPolicy());
			tableMeta.setSqlBatchNum(table.sqlBatchNum());
			Set<Field> fields = Sets.newLinkedHashSet();
			ClassUtils.findAllDeclaredFields(table.mappedSuperclass(), entityClass, fields);
		
			fields.removeIf(f -> Modifier.isTransient(f.getModifiers())
					|| AnnotationFinder.isAnnotation(f, Transient.class)
					|| AnnotationFinder.isAnnotation(f, Deprecated.class) || Modifier.isStatic(f.getModifiers()));
			
			List<ColumnMeta> columnMetas = Lists.newArrayListWithCapacity(fields.size());
			for (Field field : fields) {
				
				if (Modifier.isInterface(field.getModifiers()) || Modifier.isAbstract(field.getModifiers())) {
					Validate.isTrue(AnnotationFinder.isAnnotation(field, Customize.class),
							"抽象的成员%s必须要实现自定义序列化规则@Customize", field);
				}
				
				ColumnMeta columnMeta;
				
				if (AnnotationFinder.isAnnotation(field, Id.class)) {
					
					if (tableMeta.getIdColumnMeta() == null) {
						
						Id id = AnnotationFinder.findAnnotation(field, Id.class);
						columnMeta = new IdColumnMeta();
						((IdColumnMeta) columnMeta).setAutoIncr(id.autoIncr());
						((IdColumnMeta) columnMeta).setBeginValue(id.begin());

						tableMeta.setIdColumnMeta((IdColumnMeta) columnMeta);

					} else {
						throw new RuntimeException("不可以设计联合主键:" + entityClass);
					}
				} else {
					columnMeta = new ColumnMeta();
				}

				Nullable nullable = AnnotationFinder.findAnnotation(field, Nullable.class);
				boolean canBeNull = nullable == null ? true : nullable.value();
				if (columnMeta instanceof IdColumnMeta) {
					canBeNull = false;
				}
				columnMeta.setCanBeNull(canBeNull);
				Comment comment = AnnotationFinder.findAnnotation(field, Comment.class);
				columnMeta.setComment(comment == null ? "" : comment.value());

				columnMeta.setEntityClassName(entityClass.getName());
				columnMeta.setFieldName(field.getName());

				Method setter = null;
				Method getter = null;
				boolean customize = AnnotationFinder.isAnnotation(field, Customize.class);
				if (customize) {
					setter = ClassUtils.findDeclaredMethod(true, entityClass,
							((Customize) AnnotationFinder.findAnnotation(field, Customize.class)).setter(),
							String.class);
				} else {
					setter = ClassUtils.findSetter(entityClass, field);
				}
				if (customize) {
					getter = ClassUtils.findDeclaredMethod(true, entityClass,
							((Customize) AnnotationFinder.findAnnotation(field, Customize.class)).getter());
				} else {
					getter = ClassUtils.findGetter(entityClass, field);
				}
				columnMeta.setCustomize(customize);
				Validate.notNull(setter, "can not found setter by [%s]", field.toGenericString());
				Validate.notNull(getter, "can not found getter by [%s]", field.toGenericString());
				columnMeta.setSetter(setter.getName());
				columnMeta.setGetter(getter.getName());

				// 检查申明的类型是否是接口，是否是并发安全集合
				String[] types = new String[] { "byte", "short", "int", "long", "double", "float", "boolean",
						"byte[]" };
				if ((!ArrayUtils.contains(types, field.getType().getTypeName()))
						&& (Modifier.isInterface(field.getType().getModifiers())
								|| Modifier.isAbstract(field.getType().getModifiers()))) {
					log.debug("{} 申明类型是 {} , 请使用已实现的类型 ! ", field.toGenericString(),
							Modifier.isInterface(field.getType().getModifiers()) ? "interface"
									: Modifier.isAbstract(field.getType().getModifiers()) ? "abstract" : "unknown");
				}
				if (field.getGenericType().getTypeName().contains("Concurrent")
						|| field.getGenericType().getTypeName().contains("Blocking")
						|| field.getGenericType().getTypeName().contains("SetFrom")) {
					log.debug("{} 是并发安全对象，可使用非线程安全对象替换", field.toGenericString());
				}
				if (Modifier.isPublic(field.getModifiers())) {
					log.debug("{} 被申明成公开访问的字段，请使用private替换", field.toGenericString());
				}
				
				columnMeta.setEntityClass(entityClass);
				columnMeta.setField(field);
				columnMeta.setSetterMethod(setter);
				columnMeta.setGetterMethod(getter);
				
				// TODO
				// 字段名称之所以不提供自定义设置的规则而是直接使用java字段名转下划线
				//，是为了可以通过mysql的名称反射找到java的field用来对比java和mysql的结构变化
				columnMeta.setColumnName(StringUtils.humpToUnderline(columnMeta.getFieldName()));
				
				//部分字段需要设置
				//TODO 
				Column column = AnnotationFinder.findAnnotation(field, Column.class);

				if(column != null) {
					columnMeta.setColumnName(column.name());
				}
				
				String contentOfColumnType = contentOfColumnType(columnMeta);
				
				ImmutablePair<Long, Integer> pair = splitContentOfColumnType(contentOfColumnType);

				if (columnMeta instanceof IdColumnMeta) {
					Validate.isTrue(pair.getLeft() > 0 && pair.getLeft() <= Len.KEY, "主键[%s]长度[%s]非法",
							columnMeta.getFieldName(), pair.getLeft());
				}

				columnMeta.setColumnType(contentOfColumnType);
				columnMeta.setLength(pair.getLeft());
				columnMeta.setScale(pair.getRight());
				
			
				columnMetas.add(columnMeta);
			}

			SortUtils.quickSort(columnMetas, 0, columnMetas.size() - 1, (o1, o2) -> {
				return Integer.compare(getOrder(o1), getOrder(o2));
			});
			tableMeta.setColumnMetas(columnMetas);

			// TODO 索引部分
			Index[] indexs = table.indexes();
			if (indexs != null && indexs.length > 0) {
				IndexMeta[] indexMetas = new IndexMeta[indexs.length];
				for (int i = 0; i < indexs.length; i++) {
					Index index = indexs[i];
					IndexMeta indexMeta = indexMetas[i] = new IndexMeta();
					indexMeta.setName(index.name());
					indexMeta.setPolicy(index.policy());
					String[] columns = new String[index.fields().length];
					for (int j = 0; j < columns.length; j++) {
						String fieldName = index.fields()[j];
						Field field = ClassUtils.findDeclaredField(true, entityClass, fieldName);
						Validate.isTrue(field != null, "%s找不到字段%s", entityClass, fieldName);
						
						ColumnMeta columnMeta = TableHelper.findColumnMetaByName(tableMeta, fieldName);
						
						if (index.policy() == IndexPolicy.FULLTEXT) {
							Validate.isTrue(
									columnMeta.getColumnType().toLowerCase().contains("char")
											|| columnMeta.getColumnType().toLowerCase().contains("text"),
									"索引字段[%s]不可以设置为全文索引", columnMeta.getFieldName());
						} else {
							// datetime类型的字段索引不做长度校验
							if (columnMeta.getColumnType().equals("datetime")) {

							} else {
								
								//这个字段是索引 修改字段属性
								if(columnMeta.getColumnType().equals("tinytext")) {
									columnMeta.setColumnType(String.format("%s(%d)", "char", Len.KEY));
									columnMeta.setLength(Len.KEY);
								}
								
								Validate.isTrue(columnMeta.getLength() > 0 
										&& columnMeta.getLength() <= Len.KEY,
										"索引字段[%s]长度[%s]非法", columnMeta.getFieldName(), columnMeta.getLength());
						
							}
						}
						columns[j] = StringUtils.humpToUnderline(fieldName);
					}
					indexMeta.setColumns(columns);
				}
				tableMeta.setIndexMetas(indexMetas);
			}
			return tableMeta;
		});
	}

	private static int getOrder(ColumnMeta columnMeta) {
		return columnMeta instanceof IdColumnMeta ? -1 : 1;
	}

	// type(length,scale),例如：double(22,2)--><22,2>,得到长度和小数点位数
	public static ImmutablePair<Long, Integer> splitContentOfColumnType(String contentOfColumnType) {
		long maxLenght = 0L;
		int scale = 0;
		String[] ss1 = contentOfColumnType.split("\\(");
		if (ss1.length == 1) {
			return ImmutablePair.of(0L, 0);
		}
		String s = ss1[1].substring(0, ss1[1].length() - 1);
		String[] ss2 = s.split(",");
		if (ss2.length == 1) {
			try {
				maxLenght = Long.parseLong(s);
			} catch (NumberFormatException e) {
				e.printStackTrace();
			}
		} else if (ss2.length == 2) {
			maxLenght = Long.parseLong(ss2[0]);
			scale = Integer.parseInt(ss2[1]);
		}
		return ImmutablePair.of(maxLenght, scale);
	}

	public static String contentOfColumnType(ColumnMeta columnMeta) {
		Field field = ColumnHelper.getField(columnMeta);

		long len = 0L;
		Length length = AnnotationFinder.findAnnotation(field, Length.class);
		if (length != null) {
			len = length.value();
		}
		Blob blob = AnnotationFinder.findAnnotation(field, Blob.class);
		if (blob != null) {
			len = blob.length().value();
		}
		Text text = AnnotationFinder.findAnnotation(field, Text.class);
		if (text != null) {
			len = text.length().value();
		}
		int scalePoints = 0;
		Scale scale = AnnotationFinder.findAnnotation(field, Scale.class);
		if (scale != null)
			scalePoints = scale.value();

		Type type = field.getGenericType();
		if (type == java.time.LocalDate.class)
			throw new RuntimeException("please use [ java.time.LocalDateTime] :" + field.toGenericString());
		if (type == java.time.LocalTime.class)
			throw new RuntimeException("please use [ java.time.LocalDateTime] :" + field.toGenericString());

		if (type == boolean.class || type == Boolean.class || type == AtomicBoolean.class || type == byte.class
				|| type == Byte.class) {
			String format = "tinyint(%d)";
			int m = 4;
			if (len > 0) {
				m = (int) len;
				m = Math.min(4, m);
			}
			return String.format(format, m);
		} else if (type == short.class || type == Short.class) {
			String format = "smallint(%d)";
			int m = 6;
			if (len > 0) {
				m = (int) len;
				m = Math.min(6, m);
			}
			return String.format(format, m);
		} else if (type == int.class || type == Integer.class || type == AtomicInteger.class) {
			String format = "int(%d)";
			int m = 11;
			if (len > 0) {
				m = (int) len;
				m = Math.min(11, m);
			}
			return String.format(format, m);
		} else if (type == long.class || type == Long.class || type == BigInteger.class || type == AtomicLong.class) {
			String format = "bigint(%d)";
			int m = 20;
			if (len > 0) {
				m = (int) len;
				m = Math.min(20, m);
			}
			return String.format(format, m);
		} else if (type == float.class || type == Float.class) {
			String format = "float(%d,%d)";
			int m = 11;
			int d = 2;
			if (len > 0) {
				m = (int) len;
				m = Math.min(11, m);
			}
			if (scalePoints > 0) {
				d = scalePoints;
				d = Math.min(m - (m - d), d);
				Validate.isTrue(d >= 0, "浮点类型设置失败,column=%s", columnMeta);
			}
			return String.format(format, m, d);
		} else if (type == double.class || type == Double.class || type == AtomicDouble.class) {
			String format = "double(%d,%d)";
			int m = 20;
			int d = 2;
			if (len > 0) {
				m = (int) len;
				m = Math.min(20, m);
			}
			if (scalePoints > 0) {
				d = scalePoints;
				d = Math.min(m - (m - d), d);
				Validate.isTrue(d >= 0, "浮点类型设置失败,column=%s", columnMeta);
			}
			return String.format(format, m, d);
		} else if (type == BigDecimal.class) {
			String format = "decimal(%d,%d)";
			int m = 20;
			int d = 2;
			if (len > 0) {
				m = (int) len;
				// 1 <= len <= 65
				m = Math.max(1, m);
				m = Math.min(65, m);
			}
			if (scalePoints > 0) {
				d = scalePoints;
				// 0 <= points <= 30
				d = Math.max(0, d);
				d = Math.min(30, d);
			}
			m = Math.max(m, d);
			d = Math.min(m, d);
			d = Math.min(m - (m - d), d);
			Validate.isTrue(d <= m, "浮点类型设置失败,column=%s", columnMeta);
			return String.format(format, m, d);
		} else if (type == String.class) {
			if (len == 0) {
				if (columnMeta instanceof IdColumnMeta) {
					return String.format("%s(%d)", "char", Len.KEY);
				}
				return "text";
			}
			if (columnMeta instanceof IdColumnMeta) {
				Validate.isTrue(len > 0 && len <= Len.KEY, "主键[%s]长度[%s]非法", columnMeta.getFieldName(), len);
				return String.format("%s(%d)", "char", len);
			}
			if (len <= Len.TINY) {
				if (text != null)
					return "tinytext";
				else
					return String.format("%s(%d)", "varchar", len);
			}
			if (len <= Len.DEFAULT) {
				return "text";
			}
			if (len > Len.DEFAULT && len <= Len.MEDIUM) {
				return "mediumtext";
			}
			if (len > Len.MEDIUM) {
				return "longtext";
			}
			throw new RuntimeException("字符串列设置异常:" + columnMeta);
		} else if (type == java.time.LocalDateTime.class || type == java.util.Date.class || type == java.sql.Date.class
				|| type == java.sql.Timestamp.class) {
			return "datetime";
		} else if (type == byte[].class) {
			if (len > 0) {

				if (len <= Len.TINY) {
					return "tinyblob";
				}

				if (len > Len.DEFAULT && len <= Len.MEDIUM) {
					return "mediumblob";
				}

				if (len > Len.MEDIUM) {
					return "longblob";
				}

			}

			{
				return "blob";
			}

		} else {
			if (len > 0) {
				if (len > Len.DEFAULT && len <= Len.MEDIUM) {
					return "mediumtext";
				}
				if (len > Len.MEDIUM) {
					return "longtext";
				}
			}

			{
				return "text";
			}
		}
	}

	public static TableMeta fix(Class<?> entityClass) {
		DatabaseMeta databaseMeta = DatabaseHelper.getDatabaseMeta(entityClass);
		TableMeta tableMeta = parseTableMeta(databaseMeta, entityClass);

		if (TableHelper.pkAutoIncr(tableMeta)) {
			if (databaseMeta.getCluster() > 1)
				throw new RuntimeException("db.cluster（分库策略） 和 tab.autoincr（自增策略） 不可以同时存在，entityClass=" + entityClass);
			if (tableMeta.getCluster() > 1)
				throw new RuntimeException("tab.cluster（分表策略） 和 tab.autoincr（自增策略） 不可以同时存在，entityClass=" + entityClass);
		}

		try {
			for (String db : databaseMeta.getReallyNames()) {
				for (String tab : allTables(tableMeta)) {
					if (!existsTable(db, tab))
						createTableIfNotExists(tableMeta, db, tab);
					fixTable(tableMeta, db, tab);
				}
			}
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

		return tableMeta;
	}

	public static List<DBColumnMeta> describeTable(TableMeta tableMeta, String database, String table) {
		Class<?> entityClass = TableHelper.getEntityClass(tableMeta);
		// 查询索引字段
		List<Map<String, Object>> indexList = selectIdxList(database, table);
		// 查询db字段
		List<Map<String, Object>> columnList = BaseDaoImpl.getInstance().executeQuery(database,
				String.format("describe `%s`.`%s`", database, table));
		List<DBColumnMeta> dbColumnList = Lists.newArrayList();
		for (Map<String, Object> m0 : columnList) {
			String col_name = (String) m0.get("Field");
			String col_type = (String) m0.get("Type");
			String field_name = StringUtils.underlineToHump(col_name);
			Field col_field = ClassUtils.findDeclaredField(tableMeta.isMappedSuperclass(), entityClass, field_name);
			if (col_field == null) {
				if (tableMeta.isColumnDropable()) {
					String key = (String) m0.get("Key");
					if (!StringUtils.isEmpty(key)) {
						Map<String, Object> map = indexList.stream().filter(m -> m.get("Column_name").equals(col_name))
								.findFirst().orElseThrow(() -> new NullPointerException(
										"找不到Column_name=" + col_name + " 的索引,db.key=" + key));
						String idxName = (String) map.get("Key_name");
						BaseDaoImpl.getInstance().execute(DatabaseHelper.openConnection(database),
								String.format("alter table `%s`.`%s` drop index `%s`", database, table, idxName));
						indexList.remove(map);
					}
					BaseDaoImpl.getInstance().execute(DatabaseHelper.openConnection(database),
							String.format("alter table `%s`.`%s` drop column `%s`", database, table, col_name));
				} else {
					//log.warn(String.format("数据表[%s]中字段[%s]被丢弃或者java成员改名,entityClass=%s,field=%s", table, col_name,
					//		entityClass, field_name));
				}
				continue;
			}
			DBColumnMeta dbColumnMeta = new DBColumnMeta();
			dbColumnMeta.setFieldName(field_name);
			dbColumnMeta.setColumnName(col_name);
			dbColumnMeta.setColumnType(col_type);
			ImmutablePair<Long, Integer> pair = splitContentOfColumnType(col_type);
			dbColumnMeta.setLength(pair.getLeft());
			dbColumnMeta.setScale(pair.getRight());
			dbColumnMeta.setKey(m0);
			dbColumnMeta.setDef(m0);
			dbColumnMeta.setExtra(m0);
			dbColumnMeta.setCanBeNull(m0);
			dbColumnList.add(dbColumnMeta);
		}
		return dbColumnList;
	}

	public static List<Map<String, Object>> selectIdxList(String database, String table) {
		return BaseDaoImpl.getInstance().executeQuery(database,
				String.format("show index from `%s`.`%s`", database, table));
	}

	public static void fixTable(TableMeta tableMeta, String db, String tab) {

		if(tableMeta.isView()) {
			//视图不处理这个
			return ;
		}
		
		// 查询db字段
		List<DBColumnMeta> dbColumnList = describeTable(tableMeta, db, tab);

		// db字段和java字段对比
		List<ColumnMeta[]> addColumnList = Lists.newArrayList();
		List<ColumnMeta> modifyColumnList = Lists.newArrayList();

		for (int i = 0; i < tableMeta.getColumnMetas().size(); i++) {
			ColumnMeta columnMeta = tableMeta.getColumnMetas().get(i);
			String column_name = columnMeta.getColumnName();
			String column_type = columnMeta.getColumnType();
			DBColumnMeta db_column_meta = dbColumnList.stream()
					.filter(c -> c.getColumnName().equalsIgnoreCase(column_name)).findFirst().orElse(null);
			if (db_column_meta == null) {
				if(i - 1 >= 0) {
					ColumnMeta before = tableMeta.getColumnMetas().get(i - 1);
					addColumnList.add(new ColumnMeta[] { columnMeta, before });// 增加字段,如果是索引会同时增加
				}
			} else {
				if (!column_type.equalsIgnoreCase(db_column_meta.getColumnType())
						|| columnMeta.isCanBeNull() != db_column_meta.isCanBeNull()) {// 数据类型发生改变
					if (Properties.replace_table_column()) {
						replaceable(columnMeta, db_column_meta);
					}
					modifyColumnList.add(columnMeta);// TODO 修改字段,要考虑字段的兼容性
				}
			}
		}

		// fix
		if (!CollectionUtils.isEmpty(addColumnList)) {
			StringBuilder builder = new StringBuilder();
			builder.append(String.format("alter table `%s`.`%s` \r\n", db, tab));
			for (int i = 0; i < addColumnList.size(); i++) {
				ColumnMeta[] columnMetas = addColumnList.get(i);
				ColumnMeta columnMeta = columnMetas[0];
				ColumnMeta before = columnMetas[1];
				builder.append(String.format("add column `%s` ", columnMeta.getColumnName()));
				builder.append(columnMeta.getColumnType());
				if (before != null) {
					builder.append(String.format(" after `%s` ", before.getColumnName()));
				}
				if (i < addColumnList.size() - 1) {
					builder.append(",\r\n");
				} else {
					builder.append("\r\n");
				}
			}
			String sql = builder.toString();
			BaseDaoImpl.getInstance().execute(db, sql);
		}
		
		
		if (!CollectionUtils.isEmpty(modifyColumnList)) {

			StringBuilder builder = new StringBuilder();
			builder.append(String.format("alter table `%s`.`%s` \r\n", db, tab));
			for (int i = 0; i < modifyColumnList.size(); i++) {
				ColumnMeta columnMeta = modifyColumnList.get(i);
				builder.append(String.format("modify column `%s` ", columnMeta.getColumnName()));
				builder.append(columnMeta.getColumnType());
				builder.append(columnMeta.isCanBeNull() ? " null " : " not null ");
				if (!StringUtils.isEmpty(columnMeta.getComment())) {
					builder.append(String.format("comment '%s'", columnMeta.getComment()));
				}
				if (columnMeta instanceof IdColumnMeta) {
					if (((IdColumnMeta) columnMeta).isAutoIncr()) {
						builder.append(" auto_increment ");
					}
				}
				if (i < modifyColumnList.size() - 1) {
					builder.append(",\r\n");
				} else {
					builder.append("\r\n");
				}

			}
			String sql = builder.toString();
		
			try {
				BaseDaoImpl.getInstance().execute(db, sql);
			} catch (Exception e) {
				log.error(e.getMessage(),e);
				log.error("{}",tab);
			}
			
		}
				
		// 最后修改索引
		alterIndexs(tableMeta,db,tab);
	}

	private static void alterIndexs(TableMeta tableMeta, String db, String tab) {
		// 先处理索引
		if (tableMeta.getIndexMetas() != null && tableMeta.getIndexMetas().length > 0) {
			// 查询索引
			List<Map<String, Object>> indexList = selectIdxList(db, tab);
			for (int i = 0; i < tableMeta.getIndexMetas().length; i++) {
				IndexMeta indexMeta = tableMeta.getIndexMetas()[i];
				String name = indexMeta.getName();
				List<String> columns = Lists.newArrayList();
				for (int j = 0; j < indexMeta.getColumns().length; j++) {
					columns.add(indexMeta.getColumns()[j]);
				}
				Collections.sort(columns);
				String[] columns_array = columns.toArray(new String[columns.size()]);
				Map<String, List<String>> db_columns_map = Maps.newHashMap();
				Map<String, Boolean> db_columns_unique_map = Maps.newHashMap();
				indexList.forEach(map -> {
					String keyName;
					if ((keyName = ((String) map.get("Key_name"))).equals(name)) {
						db_columns_map.computeIfAbsent(keyName, v -> Lists.newArrayList())
								.add((String) map.get("Column_name"));
						db_columns_unique_map.computeIfAbsent(keyName, k -> {

							Object Non_unique = map.get("Non_unique");

							Long unique = Long.valueOf(String.valueOf(Non_unique));

							return unique == 0L;

						});
					}

				});
				if (db_columns_map.isEmpty() || !db_columns_map.containsKey(name)) {// 索引不存在
					BaseDaoImpl.getInstance().execute(db, indexMeta.getPolicy().alterSQL(db, tab, name, columns_array));
				} else {
					List<String> db_columns = Lists.newArrayList(db_columns_map.get(name));
					Collections.sort(db_columns);
					String[] db_columns_array = db_columns.toArray(new String[db_columns.size()]);
					if (!Arrays.equals(db_columns_array, columns_array)
							|| db_columns_unique_map.get(name) != (indexMeta.getPolicy() == IndexPolicy.UNIQUE)) {// 索引被修改
						BaseDaoImpl.getInstance().execute(db, indexMeta.getPolicy().dropSQL(db, tab, name));
						BaseDaoImpl.getInstance().execute(db,
								indexMeta.getPolicy().alterSQL(db, tab, name, columns_array));
					}
				}
			}

		}

	}

	/**
	 * 是否可以用newColumn 替换 oldColumn
	 * 
	 * @param newColumn
	 * @param oldColumn
	 */
	private static void replaceable(ColumnMeta newColumn, DBColumnMeta oldColumn) {

		if (oldColumn.getColumnType().toLowerCase().startsWith("datetime")) {
			throw new RuntimeException(String.format("%s:%s can not replace %s", newColumn.getFieldName(),
					newColumn.getColumnType(), oldColumn.getColumnType()));
		}

		String[] ints = new String[] { "tinyint", "smallint", "int", "bigint" };
		for (int i = 0; i < ints.length; i++) {
			if (oldColumn.getColumnType().toLowerCase().startsWith(ints[i])) {

				boolean match = false;
				for (int j = ints.length; --j >= i;) {
					if (!newColumn.getColumnType().toLowerCase().equals(ints[j])) {
						match = true;
						break;
					}
				}
				if (!match)
					throw new RuntimeException(String.format("%s:%s can not replace %s", newColumn.getFieldName(),
							newColumn.getColumnType(), oldColumn.getColumnType()));

				if (newColumn.getLength() < oldColumn.getLength()) {
					throw new RuntimeException(String.format("%s:%s can not replace %s", newColumn.getFieldName(),
							newColumn.getColumnType(), oldColumn.getColumnType()));
				}
			}
		}

		String[] floats = new String[] { "float", "double", "decimal" };
		for (int i = 0; i < floats.length; i++) {
			if (oldColumn.getColumnType().toLowerCase().startsWith(floats[i])) {

				boolean match = false;
				for (int j = floats.length; --j >= i;) {
					if (!newColumn.getColumnType().toLowerCase().equals(floats[j])) {
						match = true;
						break;
					}
				}
				if (!match)
					throw new RuntimeException(String.format("%s:%s can not replace %s", newColumn.getFieldName(),
							newColumn.getColumnType(), oldColumn.getColumnType()));

				if (newColumn.getLength() < oldColumn.getLength()) {
					throw new RuntimeException(String.format("%s:%s can not replace %s", newColumn.getFieldName(),
							newColumn.getColumnType(), oldColumn.getColumnType()));
				}
			}
		}

		String[] blobs = new String[] { "tinyblob", "blob", "mediumblob", "longblob" };
		for (int i = 0; i < blobs.length; i++) {
			if (oldColumn.getColumnType().toLowerCase().startsWith(blobs[i])) {
				boolean match = false;
				for (int j = blobs.length; --j > i;) {
					if (!newColumn.getColumnType().toLowerCase().equals(blobs[j])) {
						match = true;
						break;
					}
				}
				if (!match)
					throw new RuntimeException(String.format("%s:%s can not replace %s", newColumn.getFieldName(),
							newColumn.getColumnType(), oldColumn.getColumnType()));
			}
		}

		String[] chars = new String[] { "char", "varchar", "tinytext", "text", "mediumtext", "longtext" };
		for (int i = 0; i < chars.length; i++) {
			if (oldColumn.getColumnType().toLowerCase().startsWith(chars[i])) {
				boolean match = false;
				for (int j = chars.length; --j > i;) {
					if (!newColumn.getColumnType().toLowerCase().equals(chars[j])) {
						match = true;
						break;
					}
				}
				if (!match)
					throw new RuntimeException(String.format("%s:%s can not replace %s", newColumn.getFieldName(),
							newColumn.getColumnType(), oldColumn.getColumnType()));
			}
		}
	}

	public static boolean existsTable(String database, String table) {
		AtomicBoolean exists = new AtomicBoolean();
		BaseDaoImpl.getInstance().executeQuery(database, String.format(
				"select  table_schema, table_name from information_schema.tables where table_schema='%s'  and table_name = '%s'",
				database, table), rs -> {
					try {
						String db = rs.getString(1);
						String tab = rs.getString(2);
						if (db != null && tab != null)
							exists.set(db.equals(database) && tab.equals(table));
					} catch (Exception e) {
						throw new RuntimeException(e.getMessage() + ":" + database + "," + table, e);
					}
				});
		return exists.get();
	}

	public static void createTableIfNotExists(TableMeta tableMeta, String db, String tab) {
		BaseDaoImpl.getInstance().execute(db, SqlHelper.createTableIfNotExists(tableMeta, db, tab));
	}

	
	/**
	 * 此刻所有的表，如果是滚表类型是按小时那么到下个钟头就会新增一张表
	 * 
	 * @return
	 */
	public static Set<String> allTables(TableMeta tableMeta) {
		Set<String> tables = Sets.newHashSet();
		if (tableMeta.getRollPolicy() != RollPolicy.NONE) {
			DatabaseMeta databaseMeta = DatabaseHelper.getDatabaseMeta(TableHelper.getEntityClass(tableMeta));
			String db = DatabaseHelper.getOnlyReallyName(databaseMeta);// 不会分库，因为滚表和分库不能同时生效
			String sql = "show tables like '%" + tableMeta.getSimpleName() + "%'";
			BaseDaoImpl.getInstance().executeQuery(db, sql, rs -> {
				try {
					String tab = rs.getString(1);
					if (tab != null && tab.startsWith(RollPolicy.PREFIX)) {
						tables.add(tab);
					}
				} catch (Exception e) {
					throw new RuntimeException(e.getMessage() + ":" + db + "," + sql, e);
				}
			});
//			tables.add(RollPolicy.fixName(tableMeta.getSimpleName(), tableMeta.getRollPolicy()));
		} else if (tableMeta.getCluster() > 1) {
			for (int i = 0; i < tableMeta.getCluster(); i++) {
				tables.add(StringUtils.mergeJoiner("_", tableMeta.getSimpleName(), i));
			}
		} else {
			tables.add(tableMeta.getSimpleName());
		}
		return tables;
	}

	/**
	 * 滚表获取所有表
	 * @param tableMeta
	 * @param ids
	 * @return
	 */
	public static Map<String, Set<Serializable>> matchedTables(TableMeta tableMeta,
			Iterable<? extends Serializable> ids) {
		
		//分库
		if (tableMeta.getCluster() > 1) {
			Map<String, Set<Serializable>> tabIdsMap = Maps.newHashMap();
			ids.forEach(id -> tabIdsMap.computeIfAbsent(findTableByPk(tableMeta, id), k -> Sets.newHashSet()).add(id));
			return tabIdsMap;
		}
		
		//滚表
		if (tableMeta.getRollPolicy() != RollPolicy.NONE) {
			Map<String, Set<Serializable>> tabIdsMap = Maps.newHashMap();
			for (String table : allTables(tableMeta)) {
				tabIdsMap.put(table, Sets.newHashSet(ids));
			}
			return tabIdsMap;
		}
		
		//非滚表 和 分库
		return new HashMap<String, Set<Serializable>>() {
			{
				put(tableMeta.getSimpleName(), Sets.newHashSet(ids));
			}
		};
	}
	
	
	/**
	 * 可更新滚表配置
	 * @param tableMeta
	 * @param ids
	 * @return
	 * @throws Exception 
	 */
	public static Map<String, Set<RollEntity<?>>> matchedRollTables(TableMeta tableMeta,
			Iterable<? extends RollEntity<?>> entitys) throws Exception {
		//滚表
		if (tableMeta.getRollPolicy() != RollPolicy.NONE) {
			Map<String, Set<RollEntity<?>>> tabIdsMap = Maps.newHashMap();
			
			for (RollEntity<?> rollEntity : entitys) {
				String tab = getRollEntityTabName(tableMeta,rollEntity);
				Set<RollEntity<?>> sets = tabIdsMap.getOrDefault(tab, Sets.newHashSet());
				sets.add(rollEntity);
				tabIdsMap.put(tab, sets);
			}
			
			return tabIdsMap;
		}else {
			throw new RuntimeException("非滚表不继承 RollEntity tab:"+tableMeta.getSimpleName());
		}
	}
	
	/**
	 * 根据数据获取目标表名
	 * @param rollEntity
	 * @return
	 */
	public static String getRollEntityTabName(TableMeta tableMeta,RollEntity<?> rollEntity) {
		
		if(!(rollEntity instanceof RollEntity<?>)) {
			throw new RuntimeException("非滚表不继承 RollEntity tab:"+tableMeta.getSimpleName());
		}
		
		RollEntity<?> entity = (RollEntity<?>) rollEntity;
		
		Date date = entity.getNowTime();
		
		//获取目标表名
		String tabName = RollPolicy.parseName(tableMeta.getSimpleName(), tableMeta.getRollPolicy(), date.getTime());
		
		return tabName;
	}
	
	
	
	
	

	public static String findTableByPk(TableMeta tableMeta, Serializable id) {
		Validate.isTrue(tableMeta.getIdColumnMeta() != null && id != null && tableMeta.getCluster() > 1
				&& tableMeta.getRollPolicy() == RollPolicy.NONE && !TableHelper.pkAutoIncr(tableMeta));
		int hash = HashUtils.hashCode(id);
		int index = hash % tableMeta.getCluster();
		return StringUtils.mergeJoiner("_", tableMeta.getSimpleName(), index);
	}

	static public Class<?> getEntityClass(TableMeta tableMeta) {
		try {
			return (Class<?>) Class.forName(tableMeta.getEntityClassName());
		} catch (ClassNotFoundException e) {
			throw new NullPointerException(tableMeta.getEntityClassName());
		}
	}

	static public String getPkName(TableMeta tableMeta) {
		return tableMeta.getIdColumnMeta() == null ? null : tableMeta.getIdColumnMeta().getColumnName();
	}

	static public boolean pkAutoIncr(TableMeta tableMeta) {
		return tableMeta.getIdColumnMeta() != null && tableMeta.getIdColumnMeta().isAutoIncr();
	}

	static public ColumnMeta findColumnMetaByName(TableMeta tableMeta, String fieldName) {
		return tableMeta.getColumnMetas().stream().filter(c -> c.getFieldName().equals(fieldName)).findFirst()
				.orElseThrow(() -> new NullPointerException(tableMeta.getEntityClassName() + ":找不到字段：" + fieldName));
	}

}
